package server.bd;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.StringTokenizer;

import server.Medida;
import server.Sensor;

/**
 * Clase para gestionar los datos de la BD.
 * 
 * Para usarla primero se debe comprobar si el usuario existe en la BD con checkUsuario() y
 * luego tendrá que iniciar sesión con login()
 * 
 * @author josebama
 *
 */
public class GestionDatos {
	/** atributos referentes a la BD**/
    private Connection connection = null;
    private static final String DRIVER = "org.sqlite.JDBC";
    private static final String RUTA_BD = "jdbc:sqlite:bd/gestionTrenes.s3db";
    
    /**Parámetros de la clase*/
    private String usuario; //Usuario actual
    private String pass; //Contraseña del usuario actual
    private boolean init; //Para saber si se está conectado a la BD o no
    private boolean logged; //Para saber si es usuario actual ha iniciado sesión o no
    
    /**Atributos para la lectura de archivos de configuración*/
    private static final String LOGINFILE = "tren_files/login_tren";
    private File loginFile = new File(LOGINFILE);
    private static final String DATAFILE = "tren_files/datos_tren";
    private File dataFile = new File(DATAFILE);
    

    /**Constructor*/
    public GestionDatos() {
    	init = false;
    	usuario = null;
    	logged = false;
    }
    
    
    public String getUsuario() {
		return usuario;
	}


	public void setUsuario(String usuario) {
		this.usuario = usuario;
	}
	
    public String getPass() {
		return pass;
	}


	public void setPass(String pass) {
		this.pass = pass;
	}


	public boolean isInit() {
		return init;
	}


	public void setInit(boolean init) {
		this.init = init;
	}


	public boolean isLogged() {
		return logged;
	}


	public void setLogged(boolean logged) {
		this.logged = logged;
	}
	
	public static String getLoginFile() {
		return LOGINFILE;
	}


	/**
     * Inicia la conexión con la BD
     * 
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    private void init() throws ClassNotFoundException, SQLException {
    	Class.forName(DRIVER);
        connection = DriverManager.getConnection(RUTA_BD);
        this.setInit(true);
    }
    
    /**
     * Finaliza la conexión con la BD
     * 
     * @throws SQLException
     */
    public void end(boolean borrarUsuario) throws SQLException {
    	this.setInit(false);
    	if (borrarUsuario)
    		this.setUsuario(null);
        connection.close();
    }

    /**
     * Comprueba si el usuario existe en la BD
     * 
     * @param usuario Nombre del usuario que se quiere comprobar si existe en la BD
     * @return String con el resultado de la inserción del usuario
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public String checkUsuario(String usuario) throws SQLException, ClassNotFoundException {
        try {
    		FileInputStream fileInputStream = new FileInputStream(loginFile);
			InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String line = null;
			do {
				line = bufferedReader.readLine();
				if (line != null) {
					StringTokenizer st = new StringTokenizer(line, ";");
					if (usuario.equals(st.nextToken())) {
						inputStreamReader.close();
						fileInputStream.close();
						this.setUsuario(usuario);
			        	return "210 OK Bienvenido " + usuario + ".";
					}
				}
			} while (line != null);
			inputStreamReader.close();
			fileInputStream.close();
			//Si no existe finaliza la conexión con la BD y devuelve FALSE
			this.setUsuario(null);
	        return "411 ERR Falta el nombre de usuario.";
		} catch (Exception e) {
		        e.printStackTrace();
		        throw new RuntimeException("Unexpected error, check the StackTrace");
		}
    }
    
    /**
     * Método para iniciar sesión en la gestión de la BD comprobando el usuario y la contraseña
     * 
     * @param pass La contraseña del usuario
     * @return TRUE si se ha inciado sesión con éxito. FALSE si no
     * @throws SQLException
     */
    public String login(String pass) throws SQLException {
    	//Si no se ha ejecutado antes checkUsuario, error. Nunca debería pasar
    	if (usuario == null) return "600 ERR No se ha insertado un usuario previo";
    	//Si la contraseña está vacía, error
    	if (pass == null || pass == "") return "413 ERR Falta la clave.";
    	try {
    		FileInputStream fileInputStream = new FileInputStream(loginFile);
			InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String line = null;
			do {
				line = bufferedReader.readLine();
				if (line != null) {
					StringTokenizer st = new StringTokenizer(line, ";");
					if (usuario.equals(st.nextToken())) {
						String fPass = st.nextToken();
						if (pass.equals(fPass)) {
							//no lo hago con un AND para asegurarme que el orden de ejecución es el correcto
							inputStreamReader.close();
							fileInputStream.close();
							setPass(pass);
							setLogged(true);
							return "211 OK Bienvenido al sistema.";
						}
					}
				}
			} while (line != null);
			inputStreamReader.close();
			fileInputStream.close();
			return "412 ERR La clave es incorrecta.";
		} catch (Exception e) {
		        e.printStackTrace();
		        throw new RuntimeException("Unexpected error, check the StackTrace");
		}
    }
    
    /**
     * Método que devuelve la lista de sensores que tiene un tren
     * 
     * @return lista de sensores del tren
     * @throws SQLException
     * @throws ClassNotFoundException 
     */
    public List<String> getListaSensores() throws SQLException, ClassNotFoundException {
    	ArrayList<String> sensores = new ArrayList<String>();
    	String s = checkLogged();
    	if (s!=null) {
    		sensores.add(s);
    		return sensores;
    	}
    	if (!this.isInit()) init();
    	String sql = "SELECT id_sensor, encendido, descr FROM sensores";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet rs = preparedStatement.executeQuery();
    	//Recorre los resultados añadiendolos a la lista de sensores
    	while (rs.next())
        	sensores.add(new Sensor(rs.getInt(1), rs.getBoolean(2), rs.getString(3)).socketize());
    	this.end(false);
    	return sensores;
    }
    
    /**
     * Método que devuelve el historial de medidas tomadas por un sensor
     * 
     * @param idSensor ID del sensor del que se quiere recibiir el historial
     * @return Lista de medidas que componen el histortial del sensor 
     * @throws SQLException
     * @throws ClassNotFoundException 
     */
    public List<String> getHistorialSensor(int idSensor) throws SQLException, ClassNotFoundException {
    	ArrayList<String> medidas = new ArrayList<String>();
    	String s = checkLogged();
    	if (s != null) {
    		medidas.add(s);
    		return medidas;
    	}
        if (!this.isInit()) init();
    	//Comprbar si existe el sensor
    	try {
    		isSensorOn(idSensor);
    	} catch (SensorNoExisteException e) {
    		this.end(false);
    		medidas.add("424 ERR Sensor desconocido.");
    		return medidas;
    	}
    	String sql = "SELECT id_medida, id_sensor, fecha, hora, coordenadas, valor FROM medidas WHERE id_sensor=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, idSensor);
        ResultSet rs = preparedStatement.executeQuery();
    	//Resorre los resultados añadiendolos a la lista de sensores
    	while (rs.next())
        	medidas.add(new Medida(rs.getInt(1), rs.getInt(2), rs.getString(3), rs.getString(4), rs.getString(5), rs.getFloat(6)).socketize());
    	this.end(false);
    	return medidas;
    }
    
    /**
     * Método para saber si el sensor está o no encendido
     * 
     * @param idSensor ID del sensor que se quiere saber si está o no encendido
     * @return TRUE si está encendido, FALSE si está apagado
     * @throws SQLException
     * @throws SensorNoExisteException Salta en caso de que el sensor no exista
     */
    private boolean isSensorOn(int idSensor) throws SQLException, SensorNoExisteException {
    	String sql = "SELECT encendido FROM sensores WHERE id_sensor=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, idSensor);
        ResultSet rs = preparedStatement.executeQuery();
        if (rs.next())
        	return rs.getBoolean(1);
        throw new SensorNoExisteException();
    }
    
    /**
     * Método para encender/apagar un sensor
     * 
     * @param idSensor El ID del sensor a encender/apagar
     * @param on Si se quiere encender o apagar el sensor
     * @return Un string con el código de error o de acierto que viene en el DTE
     * @throws SQLException
     */
    private String switchSensor(int idSensor, boolean on) throws SQLException {
    	String s = checkLogged();
    	if (s != null) return s;
    	try {
    		if (isSensorOn(idSensor) == on) { //si el sensor ya está en el estado al que se quería cambiar
        		if (on) return "428 ERR Sensor en estado ON."; //se quería encender
        		return "429 ERR Sensor en estado OFF."; //se quería apagar
        	}
    	} catch (SensorNoExisteException e) {
    		return "427 ERR Sensor no existe."; //El sensor no existe
    	}
    	//en caso de que todo esté bien, continúa
    	String sql = "UPDATE sensores SET encendido = ? WHERE id_sensor = ?";
		PreparedStatement preparedStatement = connection.prepareStatement(sql);
		preparedStatement.setInt(2, idSensor);
		preparedStatement.setBoolean(1, on);
		preparedStatement.executeUpdate();
		//Se ha encendido/apagado. Mostrar el mensaje de acierto
		if (on) return "213 OK Sensor activo."; //se quería encender
		return "214 OK Sensor desactivado."; //se quería apagar
    }
    
    /**
     * Método para encender un sensor
     * 
     * @param idSensor ID del sensor a encender
     * @return Un string con el código de error o de acierto que viene en el DTE
     * @throws SQLException
     * @throws ClassNotFoundException 
     */
    public String encenderSensor(int idSensor) throws SQLException, ClassNotFoundException {
        if (!this.isInit()) init();
    	String mensaje = switchSensor(idSensor, true);
    	end(false);
    	return mensaje;
    }
    
    /**
     * Método para apagar un sensor
     * 
     * @param idSensor ID del sensor a encender
     * @return Un string con el código de error o de acierto que viene en el DTE
     * @throws SQLException
     * @throws ClassNotFoundException 
     */
    public String apagarSensor(int idSensor) throws SQLException, ClassNotFoundException {
        if (!this.isInit()) init();
    	String mensaje = switchSensor(idSensor, false);
    	end(false);
    	return mensaje;
    }
    
    /**
     * Método para saber si el GPS está o no encendido
     * 
     * @param idTren ID del tren del que se quiere saber si teie el GPS encendido
     * @return TRUE si está encendido, FALSE si está apagado
     * @throws SQLException
     */
    private boolean isGPSOn() throws SQLException {
    	try {
    		FileInputStream fileInputStream = new FileInputStream(dataFile);
			InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String line = bufferedReader.readLine();
			boolean on;
			if (line != null) {
				StringTokenizer st = new StringTokenizer(line, ";");
				st.nextToken();
				on = Boolean.parseBoolean(st.nextToken());
			} else {
				inputStreamReader.close();
				fileInputStream.close();
				throw new RuntimeException("El archivo está vacío");
			}
			inputStreamReader.close();
			fileInputStream.close();
			return on;
		} catch (Exception e) {
	        e.printStackTrace();
	        throw new RuntimeException("Unexpected error, check the StackTrace");
		}
    }
    
    /**
     * Método para encender/apagar el GPS de un tren
     * 
     * @param on Si se quiene encender o apagar el GPS
     * @return El string con el mensaje de error o éxito del DTE
     * @throws SQLException
     */
    private String switchGPS(boolean on) throws SQLException {
    	String s = checkLogged();
    	if (s != null) return s;
    	if (isGPSOn() == on) { //si el GPS ya está en el estado al que se quería cambiar
    		if (on) return "429 ERR GPS en estado ON."; //se quería encender
    		return "430 ERR GPS en estado OFF."; //se quería apagar
    	}
    	//en caso de que todo esté bien, continúa
    	try {
    		FileInputStream fileInputStream = new FileInputStream(dataFile);
			InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String line = bufferedReader.readLine();
			String nueva = null;
			if (line != null) {
				StringTokenizer st = new StringTokenizer(line, ";");
				//No lo concanteno en una misma sentencia para asegurarme del orden de ejecución
				nueva = st.nextToken()+";";
				nueva += on+";";
				st.nextToken();
				nueva += st.nextToken()+";";
				nueva += st.nextToken();
			} else {
				inputStreamReader.close();
				fileInputStream.close();
				throw new RuntimeException("El archivo está vacío");
			}
			inputStreamReader.close();
			fileInputStream.close();
			BufferedWriter output = new BufferedWriter(new FileWriter(dataFile));
			output.write(nueva);
			output.close();
			//Se ha encendido/apagado. Mostrar el mensaje de acierto
			if (on) return "215 OK GPS activado."; //se quería encender
			return "216 OK GPS desactivado."; //se quería apagar
		} catch (Exception e) {
	        e.printStackTrace();
	        throw new RuntimeException("Unexpected error, check the StackTrace");
		}

    }
    
    /**
     * Método para encender el GPS de un tren
     * 
     * @return String con el mensaje de erro o éxito del DTE
     * @throws SQLException
     */
    public String encenderGPS() throws SQLException {
    	return switchGPS(true);
    }
    
    /**
     * Método para apagar el GPS de un tren
     * 
     * @return String con el mensaje de erro o éxito del DTE
     * @throws SQLException
     */
    public String apagarGPS() throws SQLException {
    	return switchGPS(false);
    }
    
    /**
     * Método que devuelve un string con los datos de la medida tomada o un mensaje de error
     * 
     * @param idSensor
     * @return
     * @throws SQLException 
     * @throws ClassNotFoundException 
     */
    public String getMedidaActual(int idSensor) throws SQLException, ClassNotFoundException {
    	checkLogged();
    	if (!this.isInit()) init();
    	try {
	    	if (!isSensorOn(idSensor))
	    		return "426 ERR Sensor en OFF."; //el sensor está apagado
    	} catch (SensorNoExisteException e) {
    		return "424 ERR Sensor desconocido.";
    	}
    	String sql = "insert into medidas (id_sensor, fecha, hora, coordenadas, valor) values (?,?,?,?,?)";
		PreparedStatement preparedStatement = connection.prepareStatement(sql);
		preparedStatement.setInt(1, idSensor);
		Calendar c = new GregorianCalendar();
		preparedStatement.setString(2, Integer.toString(c.get(Calendar.DATE))+"/"+Integer.toString(c.get(Calendar.MONTH)+1)+"/"+Integer.toString(c.get(Calendar.YEAR)));
		preparedStatement.setString(3, Integer.toString(c.get(Calendar.HOUR))+":"+Integer.toString(c.get(Calendar.MINUTE))+":"+Integer.toString(c.get(Calendar.SECOND)));
		preparedStatement.setString(4, loc());
		preparedStatement.setDouble(5, 120);
		preparedStatement.executeUpdate();
    	
    	sql = "select max(id_medida) from medidas where id_sensor=?";
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, idSensor);
        ResultSet rs = preparedStatement.executeQuery();
        if (rs.next()) {
        	int maxMedida = rs.getInt(1);
        	sql = "SELECT id_medida, id_sensor, fecha, hora, coordenadas, valor FROM medidas WHERE id_medida = ?";
        	preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, maxMedida);
            rs = preparedStatement.executeQuery();
            if (rs.next()) {
            	String medida = new Medida(rs.getInt(1), rs.getInt(2), rs.getString(3), rs.getString(4), rs.getString(5), rs.getFloat(6)).socketize();
            	end(false);
        		return "124 OK " + medida;
            }
        }
        end(false);
        return "";
    }
    
    private String loc() {
    	try {
    		FileInputStream fileInputStream = new FileInputStream(dataFile);
			InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String line = bufferedReader.readLine();
			String loc = null;
			if (line != null) {
				StringTokenizer st = new StringTokenizer(line, ";");
				//No lo concanteno en una misma sentencia para asegurarme del orden de ejecución
				st.nextToken();st.nextToken();st.nextToken();
				loc = st.nextToken();
			} else {
				fileInputStream.close();
				throw new RuntimeException("El archivo está vacío");
			}
			fileInputStream.close();
			return loc;
    	} catch (Exception e) {
	        e.printStackTrace();
	        throw new RuntimeException("Unexpected error, check the StackTrace");
		}
    }
    
    /**
     * Método que devuelve las coordenadas GPS del tren
     * 
     * XXX ¿Y si el GPS está OFF?
     * 
     * @param idTren Tren del que se quire saber la posición
     * @return
     */
    public String getLoc() {
    	return "124 OK "+loc();
    }
    
    public String salir() {
    	return "218 OK Adios.";
    }
    
    public boolean checkTrenMantenimiento() throws SQLException {
    	try {
    		FileInputStream fileInputStream = new FileInputStream(dataFile);
			InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String line = bufferedReader.readLine();
			String estado = null;
			if (line != null) {
				StringTokenizer st = new StringTokenizer(line, ";");
				st.nextToken();st.nextToken();
				estado = st.nextToken();
			} else {
				fileInputStream.close();
				throw new RuntimeException("El archivo está vacío");
			}
			fileInputStream.close();
			return estado.equals("En mantenimiento");
		} catch (Exception e) {
	        e.printStackTrace();
	        throw new RuntimeException("Unexpected error, check the StackTrace");
		}
    }
    
    /**
     * Método para comprobar si el usuario está loggeado o no
     */
    private String checkLogged() {
    	if (!this.isLogged()) notLogged(); //Si no está loggeado, error
    	return null;
    }
    
    /**
     * Método que cerrará la conexión con la BD y parará la ejecución por un mal uso de esta clase
     */
    private String notLogged() {
    	try {
    		this.end(true);
    	} catch (SQLException e) {
    		e.printStackTrace();
    	}
    	return "601 ERR El usuario debe haber iniciado sesion";
    }
    
    class SensorNoExisteException extends Exception {

		/**
		 * 
		 */
		private static final long serialVersionUID = 3665532606204404793L;
    	
    };
}
